using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace CGSR.Core
{
	public class Node
	{
		#region Properties

		private const int bandwidth = 5;

		public int ID { get; set; }
		public int X { get; set; }
		public int Y { get; set; }

		public double Power { get; set; }
		private int bufferSize = 0;
		public int bufferSizeLimit { get; private set; }
		public List<Packet> Buffer { get; private set; }
		public int clusterHead;

		public bool IsCH
		{
			get
			{
				return clusterHead == ID;
			}
		}
		#region routing tables fo CHs
		/// <summary>
		/// Dictionary from destination CH's id to a routing record to it
		/// </summary>
		Dictionary<int, RoutingRecord> clusterHeadsRoutingTable = new Dictionary<int, RoutingRecord>();
		Dictionary<int, ClusterMembershipRecord> clusterMembershipTable = new Dictionary<int, ClusterMembershipRecord>();
		
		Dictionary<int, RoutingRecord> clusterHeadsRoutingTableToPrapagate = new Dictionary<int, RoutingRecord>();
		Dictionary<int, ClusterMembershipRecord> clusterMembershipTableToPrapagate = new Dictionary<int, ClusterMembershipRecord>();


		//private List<Packet> receivedPackets;

		#endregion
		public bool isRoutingTablesDirty;

		// ACTUAL direct neighbor CHs. for a CH this should be empy
		private IEnumerable<int> neighborCHs
		{
			get
			{
				return neighborNodesToCluterheads.Where(x => x.Key == x.Value).Select(x => x.Key);
			}
		}

		/// <summary>
		/// this is only for CHs. It contains only other CHs that are immidiate neighbors of me
		/// meaning, reacheable throu 1 or 2 GWs.
		/// </summary>
		public Dictionary<int, int> virtuallyneighborCHtoGw = new Dictionary<int, int>();

		/// <summary>
		///  This dictionary holds for each node (node's Id) the amount of messages (directly) sent
		///  from me to that node in the current Tick. The amount cannot exceed 5 per Tick.
		///  It needs to be cleaned every tick.
		/// </summary>
		public Dictionary<int, int> BandwidthCounter = new Dictionary<int, int>();

		#region Private

		private SimulationManager simulationManager;


		//nodes I've heard and their chosen CH
		private Dictionary<int, int> neighborNodesToCluterheads = new Dictionary<int, int>();

		/// <summary>
		/// from cluster heads that are 2 hops away from me
		/// i.e neighbor CHs of my neighbors
		/// to the node between us
		/// </summary>
		private Dictionary<int, int> twoHopsCHs = new Dictionary<int, int>();

		#endregion

		#endregion

		#region C'tors

		public Node(int id, double power, int bufferSizeLimit)
		{
			this.ID = id;
			this.Power = power;
			this.bufferSizeLimit = bufferSizeLimit;
			this.Buffer = new List<Packet>();
		}

		public Node(int id, int x, int y, double power, int bufferSize, SimulationManager sm)
			: this(id, power, bufferSize)
		{
			this.simulationManager = sm;
			this.X = x;
			this.Y = y;
		}

		#endregion

		#region Initial Clustering

		private List<int> initialClusteringNeighbors = new List<int>();

		public void ClusterPhaseOne()
		{
			initialClusteringNeighbors.Add(this.ID);
			this.simulationManager.Probe(this, initialClusteringNeighbors, null);
			//Acks.Clear(); - It can be that i was probed before my broadcast, so i'll lose my neighbors.
		}

		public void ClusterPhaseTwo()
		{
			// if i heard a node who is a cluster head with lower ID, set him to be my cluster head.

			// Get all my CH neighbors
			IEnumerable<int> neighborClusterHeads = neighborNodesToCluterheads.Where(m => m.Key == m.Value).Select(m => m.Key);

			// Set it to be my cluster head if its' lower then my ID. Otherwise - I'm CH.
			this.clusterHead = neighborClusterHeads.Count() < 1 ? this.ID : Math.Min(this.ID, neighborClusterHeads.Min());

			neighborNodesToCluterheads.Add(this.ID, clusterHead);
			simulationManager.Probe(this, initialClusteringNeighbors, clusterHead);
		}

		internal void Probed(Node probingNode, int? clusterHead)
		{
			initialClusteringNeighbors.Add(probingNode.ID);
			if (clusterHead != null)
				neighborNodesToCluterheads.Add(probingNode.ID, (int)clusterHead);
		}

		public bool CanHear(Node otherNode)
		{
			return this.simulationManager.GetDistanceBetweenNodes(this, otherNode)
				<= this.simulationManager.receptionRadius;
			//return (this.X - otherNode.X) * (this.X - otherNode.X) + (this.Y - otherNode.Y) * (this.Y - otherNode.Y) <=
			//       this.simulationManager.receptionRadius.sqr();
		}

		public bool CanHear(int otherNodeId)
		{
			return this.CanHear(this.simulationManager.Nodes[otherNodeId]);
		}

		/// <summary>
		/// for CHs to be able to choose GWs
		/// notify my neighbors of all the CHs I can hear
		/// </summary>
		internal void broadcastCHs()
		{
			simulationManager.broadcastCHs(this, neighborCHs, neighbors);
		}

		private IEnumerable<int> neighbors
		{
			get { return neighborNodesToCluterheads.Keys; }
		}

		/// <summary>
		/// update CHs that are neighbors of my neighbors
		/// </summary>
		/// <param name="sourceId"></param>
		/// <param name="reachableCHs"></param>
		public void UpdateTwoHopCHs(int sourceId, IEnumerable<int> reachableCHs)
		{
			if (this.IsCH)
			{
				this.UpdatePossibleGW(sourceId, reachableCHs);
			}
			else
			{
				foreach (var reachableCH in reachableCHs)
				{
					if (!this.twoHopsCHs.ContainsKey(reachableCH))
						this.twoHopsCHs[reachableCH] = sourceId;
				}
			}
		}

		internal void UpdatePossibleGW(int sourceId, IEnumerable<int> reachableCHs)
		{
			if (!IsCH) return;

			foreach (int reachableCH in reachableCHs)
			{
				if (reachableCH != ID && !virtuallyneighborCHtoGw.ContainsKey(reachableCH))
				{
					virtuallyneighborCHtoGw[reachableCH] = sourceId;
				}

			}
		}

		/// <summary>
		/// Notify my neighbor CHs of CHs that are reachable from me with 2 hops
		/// i.e CHs that are my neighbors' neighbors
		/// </summary>
		public void broadcastReachableCHs()
		{
			simulationManager.broadcastReachableCHs(this, this.twoHopsCHs.Keys, this.neighborCHs);
		}

		#endregion

		#region Reclustering

		public List<Node> newneighbors = new List<Node>();

		/// <summary>
		/// find all interfering cluster heads
		/// </summary>
		public void ReclusterPhaseOne()
		{
			neighborNodesToCluterheads.Clear();
			virtuallyneighborCHtoGw.Clear();
			twoHopsCHs.Clear();
			newneighbors = simulationManager.neighbors(this);
			if (IsCH)// && newneighbors.Any(x => (x.ID < ID && x.IsCH)))
			{
				Node minneighborCH = newneighbors.Where(x => (x.ID < ID && x.IsCH)).FirstOrDefault();
				if (minneighborCH != null)
					clusterHead = minneighborCH.ID; //if I can now hear a CH with lower ID, I will give up my role as a CH
			}
		}

		/// <summary>
		/// after this, every node knows its (new) CH
		/// </summary>
		public void ReclusterPhaseTwo()
		{
			if (!IsCH && !CanHear(clusterHead)) // I can no longer hear my CH
			{
				var newCH = newneighbors.FirstOrDefault(x => x.IsCH);
				if (newCH != null) // I can hear another CH
					clusterHead = newCH.ID;
				else // I hear no CH -> I will be my own CH
					clusterHead = ID;
			}
			simulationManager.broadcastNewCH(this, clusterHead);
		}

		public void UpdateNewCH(int node, int ch)
		{
			neighborNodesToCluterheads[node] = ch;
		}

		#endregion

		#region Routing



		/// <summary>
		/// Perform all actions needed at the beginning of a new tick
		/// </summary>
		public void NewTick()
		{
			/// Clean the BandwidthCounter.
			BandwidthCounter.Clear();
			if (isRoutingTablesDirty)
			{
				PropagateRoutingTables();
				
				
			}
		}

		public void Move(Location newLocation)
		{
			X = newLocation.X;
			Y = newLocation.Y;
		}

		/// <summary>
		/// recalculate the routing tables. see if there's new info
		/// </summary>
		internal void RecalcRoutingTables()
		{
			foreach (int clusterHead in virtuallyneighborCHtoGw.Keys)
			{
				if (!clusterHeadsRoutingTable.ContainsKey(clusterHead) || clusterHeadsRoutingTable[clusterHead].distance > 1)
				{
					var routingRecord = new RoutingRecord(clusterHead, 1, simulationManager.currentTick);
					clusterHeadsRoutingTable[clusterHead] = routingRecord;

					clusterHeadsRoutingTableToPrapagate[clusterHead] = routingRecord;
					isRoutingTablesDirty = true;
				}
			}

			foreach (KeyValuePair<int, int> myMember in neighborNodesToCluterheads.Where(x => x.Value == ID))
			{
				if (!clusterMembershipTable.ContainsKey(myMember.Key) || clusterMembershipTable[myMember.Key].ClusterHead != ID)
				{
					var clusterMembershipRecord = new ClusterMembershipRecord(myMember.Key, ID, simulationManager.currentTick);
					clusterMembershipTable[myMember.Key] = clusterMembershipRecord;

					clusterMembershipTableToPrapagate[myMember.Key] = clusterMembershipRecord;
					isRoutingTablesDirty = true;
				}
			}



		}

		internal void PropagateRoutingTables()
		{
			foreach (var CH in virtuallyneighborCHtoGw.Keys)
			{
				EnqueuePacket(new RoutingInfoPacket(-1, 1, ID, CH, clusterHeadsRoutingTableToPrapagate, clusterMembershipTableToPrapagate));
			}
			clusterMembershipTableToPrapagate = new Dictionary<int, ClusterMembershipRecord>();
			clusterHeadsRoutingTableToPrapagate = new Dictionary<int, RoutingRecord>();
			isRoutingTablesDirty = false;
		}

		public void ReceivePacket(Packet packet)
		{
			if (packet.Destination == this.ID) /// I'm the destination! 
			{
				if (packet is RoutingInfoPacket)
				{
					UpdateMyRoutingTables((packet as RoutingInfoPacket));
				}
				else
				{
					this.simulationManager.PacketReceived(this, packet);
				}
			}
			else /// I'm not the destination of the packet so store it in my buffer and 
			{
				EnqueuePacket(packet);
			}
		}


		/// <summary>
		/// Send packets in current tick while available
		/// </summary>
		internal void TransferPackets()
		{
			var newBuffer = new List<Packet>();
			foreach (var packet in Buffer)
			{
				int? tempNextHop;

				tempNextHop = GetNextHop(packet);

				if (tempNextHop == null)
				{
					//not enough information
					simulationManager.PacketFailedToReachDestination(packet);
					continue;
				}

				int nextHop = tempNextHop.Value;

				if (nextHop == -1)
					simulationManager.PacketFailedToReachDestination(packet);

				if (!BandwidthCounter.ContainsKey(nextHop))
					BandwidthCounter[nextHop] = 0;

				if (BandwidthCounter[nextHop] + packet.Size > bandwidth)
				{
					newBuffer.Add(packet);
				}
				else
				{
					// Check if there is enough power left
					double neededPower =
						this.simulationManager.ComputeNeededPowerForSendingPacket(
							this.simulationManager.GetDistanceBetweenNodes(this.ID, nextHop));
					if (neededPower <= this.Power)
					{
						this.Power -= neededPower;
						this.BandwidthCounter[nextHop] += packet.Size;
						packet.NextHop = nextHop;
						this.simulationManager.SendPacket(packet, this);
					}
					else /// Not enough power to send packet
					{
						this.simulationManager.PacketFailedToReachDestination(packet);
					}
				}
			}
			Buffer = newBuffer;
			bufferSize = Buffer.Sum(x => x.Size);
		}

		public int? GetNextHop(Packet packet)
		{
			if (this.IsCH)   // I'm a clusterhead
			{
				if (this.CanHear(packet.Destination)) // I can hear the destination
				{
					return packet.Destination;
				}
				else
				{
					if (virtuallyneighborCHtoGw.ContainsKey(packet.Destination)) // The destination is a neighbor CH of mine
					{
						packet.NextCH = packet.Destination;
						return virtuallyneighborCHtoGw[packet.Destination];
					}
					else // I can't hear the destination node, and it is not a neighbor CH of mine
					{
						// Need to look for the next CH in the routing table
						if (!clusterMembershipTable.ContainsKey(packet.Destination) ||
							!clusterHeadsRoutingTable.ContainsKey(clusterMembershipTable[packet.Destination].ClusterHead))
							return null;

						packet.NextCH = clusterHeadsRoutingTable[clusterMembershipTable[packet.Destination].ClusterHead].nextHop;
						if (!virtuallyneighborCHtoGw.ContainsKey(packet.NextCH))
							return null; //I can no longer reach the next CH in my routing table
						return virtuallyneighborCHtoGw[packet.NextCH];
					}
				}
			}
			else  // I'm not a CH so I'm a GW
			{
				if (neighborNodesToCluterheads.ContainsKey(packet.Destination)) // the packet is for a node I hear
				{
					return packet.Destination;
				}
				else // I can't hear the destination. 
				{
					// Check if it's for a CH that one of my neighbors can hear
					if (twoHopsCHs.ContainsKey(packet.Destination))
					{
						// I can send the message through my neighbor
						return twoHopsCHs[packet.Destination];
					}
					else
					{
						// the destination is not my neighbor, nor a neighbor CH of one of my neighbors
						// therefore I'll transfer the message to the next CH (if defined), he'll know what to do
						int nextHop;
						if (packet.NextCH == -1)
						{ // no next CH defined. I'll send to my CH
							nextHop = clusterHead;
						}
						else
						{
							//Next CH defined. lets transfer the message to it
							if (neighborNodesToCluterheads.ContainsKey(packet.NextCH))
							{
								// I'm a GW, and the next CH is my neigbour
								nextHop = packet.NextCH;
							}
							else
							{
								// I'm a GW, and the next CH is NOT my neigbour, Need to send to the next GW
								if (twoHopsCHs.ContainsKey(packet.NextCH))
									nextHop = twoHopsCHs[packet.NextCH];
								else
								{
									//I can no longer reach the wanted CH, I'll return the packet to my CH
									nextHop = clusterHead;
								}
							}
						}
						return nextHop;
					}
				}
			}
		}

		private void UpdateMyRoutingTables(RoutingInfoPacket routingInfo)
		{
			if (virtuallyneighborCHtoGw.ContainsKey(routingInfo.Source))
			{
				// need to make sue the surce of the information is still a neighbor CH of mine
				// otherwise no reson to update clusterHeadsRoutingTable, only clusterMembershipTable
				foreach (KeyValuePair<int, RoutingRecord> routingRecord in routingInfo.RoutingTable)
				{
					if (routingRecord.Key == ID)
						continue;

					if (!clusterHeadsRoutingTable.ContainsKey(routingRecord.Key) || //no info
						clusterHeadsRoutingTable[routingRecord.Key].tickOrigin < routingRecord.Value.tickOrigin ||
						// new info is more recent
						(clusterHeadsRoutingTable[routingRecord.Key].tickOrigin == routingRecord.Value.tickOrigin &&
						//(new info is from the same time
						 clusterHeadsRoutingTable[routingRecord.Key].distance > routingRecord.Value.distance + 1))
					// but path is better)
					{
						var record = new RoutingRecord(routingInfo.Source, routingRecord.Value.distance + 1, routingRecord.Value.tickOrigin);
						clusterHeadsRoutingTable[routingRecord.Key] =record;

						clusterHeadsRoutingTableToPrapagate[routingRecord.Key] = record;
						isRoutingTablesDirty = true;
					}
				}
			}

			foreach (ClusterMembershipRecord clusterMembershipRecord in routingInfo.ClusterMembership.Values)
			{
				if (!clusterMembershipTable.ContainsKey(clusterMembershipRecord.Node) || // no info about that node
					clusterMembershipTable[clusterMembershipRecord.Node].tickOrigin < clusterMembershipRecord.tickOrigin) //new info is more recent
				{
					clusterMembershipTable[clusterMembershipRecord.Node] = clusterMembershipRecord;
					clusterMembershipTableToPrapagate[clusterMembershipRecord.Node] = clusterMembershipRecord;
					isRoutingTablesDirty = true;
				}
			}
		}

		#endregion

		public void EnqueuePacket(Packet packet)
		{
			if (bufferSize + packet.Size > this.bufferSizeLimit)
				simulationManager.PacketFailedToReachDestination(packet);
			else
			{
				Buffer.Add(packet);
				bufferSize += packet.Size;
			}
		}


		public void dropAllPackets()
		{
			foreach (var packet in Buffer)
			{
				simulationManager.PacketFailedToReachDestination(packet);
			}
		}
	}
}